Framework.Modules = {}

local ModulesIndex = {}

local function IndexingWithInheritance( self, Index )
	if( rawget( self, Index ) ) then
		return rawget( self, Index )
	end
	
	for k, v in pairs( self._Parents ) do
		local Module = Framework.Modules.Get( v )
		if( Module[Index] != nil ) then
			return Module[Index]
		end
	end
end	

function Framework.Modules.New( ST_Name, OP_ST_TA_InheritsFrom, OP_ST_TA_Dependencies )
	OP_ST_TA_InheritsFrom = OP_ST_TA_InheritsFrom or {}
	OP_ST_TA_Dependencies = OP_ST_TA_Dependencies or {}
	if( type( OP_ST_TA_InheritsFrom ) == "string" ) then OP_ST_TA_InheritsFrom = {OP_ST_TA_InheritsFrom} end
	if( type( OP_ST_TA_Dependencies ) == "string" ) then OP_ST_TA_Dependencies = {OP_ST_TA_Dependencies} end
	if( ST_Name != "Base" ) then table.insert( OP_ST_TA_InheritsFrom, "Base" ) end
	
	local t = { _Name = ST_Name, _Parents = OP_ST_TA_InheritsFrom, _Dependencies = OP_ST_TA_Dependencies, _Running = false }
	t.__index = IndexingWithInheritance
	
	setmetatable( t, t )
	table.insert( ModulesIndex, t )
	Framework.Statistics.New( ST_Name .. " Hook Data", STATISTIC_COLLECTION )
	Framework.Statistics.New( ST_Name .. " Total Time", STATISTIC_SINGLE )
	return t
end

function Framework.Modules.Get( ST_Name )
	for k, v in pairs( ModulesIndex ) do
		if( v._Name == ST_Name ) then
			return v
		end
	end
	return false
end

function Framework.Modules.GetAll()
	return ModulesIndex
end

function Framework.Modules.GetStatistics( ST_Name )
	return { Total = Framework.Statistics.Get( ST_Name .. " Total Time" ), Hooks = Framework.Statistics.Get( ST_Name .. " Hook Data" ) }
end

local hOldCall = hook.Call
hook.Call = function( Hook, GMode, ... )
	if ( Hook == "ShutDown" ) then 
		hook.Call( "Disable", GMode, ... )
	end
	for _, Module in pairs( ModulesIndex ) do	
		if( Module:IsRunning() ) then
			if( Module[Hook] ) then
				local TimeData = Framework.Statistics.Get( Module:GetName() .. " Hook Data" )
				local Time = os.clock()
				local r = { Module[Hook]( Module, ... ) }
				if( #r > 0 ) then
					return unpack( r )
				end
				local Diff = os.clock() - Time
				TimeData[Hook] = ( TimeData[Hook] or 0 ) + Diff
				Framework.Statistics.Set( Module:GetName() .. " Total Time", Framework.Statistics.Get( Module:GetName() .. " Total Time" ) + Diff )
			end
		end
	end
	return hOldCall( Hook, GMode, ... )
end

local Base = Framework.Modules.New( "Base", {}, {} )

function Base:OnEnabled()  end
function Base:OnDisabled() end

function Base:GetName() 		return self._Name 			end
function Base:IsRunning() 		return self._Running 		end
function Base:GetDependencies() return self._Dependencies 	end
function Base:Enable( BL_EnableOnClient )
	if( self._Running ) then return true end
	self.__Enabling__ = true
	for k, v in pairs( self._Dependencies ) do
		local Module = Framework.Modules.Get( v )
		if( !Module:IsRunning() ) then
			if( Module.__Enabling__ ) then
				Framework.IO.Error( "Failed to load module '" .. self:GetName() .. "': Circular dependency detected with '" .. Module:GetName() .. "'" )
				return false
			end
			if( !Module:Enable() ) then
				Framework.IO.Error( "Failed to load module '" .. self:GetName() .. "': Dependency '" .. Module:GetName() .. "' failed to load" )
				return false
			end
		end
	end
	self.__Enabling__ = false
	self._Running = true
	self:OnEnabled()
	if( SERVER && BL_EnableOnClient ) then
		self:EnableClientside( true )
	end
	return true
end

function Base:Disable( BL_DisableClientside )
	if( !self._Running ) then return true end
	self.__Disabling__ = true
	for _, Module in pairs( ModulesIndex ) do
		if( Module:IsRunning() && table.HasValue( Module._Dependencies, self:GetName() ) ) then
			-- Technically circular dependencies when disabling shouldn't be possible, because they would've been detected when loading, but I'm sure people will find a way..
			if( !Module.__Disabling__ ) then
				Module:Disable()
			end
		end
	end
	self.__Disabling__ = false
	self._Running = false
	self:OnDisabled()
	if( SERVER && BL_DisableOnClient ) then
		self:EnableClientside( false )
	end
	return true
end

if( SERVER ) then
	function Base:EnableClientside( BL_Enable, PL_Player )
		umsg.Start( "Framework.Modules:ToggleClientside", PL_Player or nil )
			umsg.String( self:GetName() )
			umsg.Bool( BL_Enable )
		umsg.End()
	end
else
	local LoadBuffer = {}
	usermessage.Hook( "Framework.Modules:ToggleClientside", function( um )
		local ModuleName = um:ReadString()
		local Status = um:ReadBool()
		local Module = Framework.Modules.Get( ModuleName )
		if( !Module ) then
			table.insert( LoadBuffer, { Load = Status, Name = ModuleName, Timestamp = CurTime() } )
			return
		end
		if( Status ) then Module:Enable() else Module:Disable() end
	end )
	
	-- Give the clientside modules 5 seconds grace period before concluding that it cannot enable or disable itself.
	hook.Add( "Think", "Framework.Modules.CheckLoadBuffer", function() 
		for k, v in pairs( LoadBuffer ) do
			local Module = Framework.Modules.Get( v.Name )
			if( !Module && CurTime() - v.Timestamp > 5 || ( Module && (v.Load && Module:Enable() || Module:Disable()) != "Remove Anyway" ) ) then 
				table.remove( LoadBuffer, k )
			end				
		end
	end )
end


/*************************************************************************************
*** Example of a module showing the time spent in each hook of each running module ***
**************************************************************************************

local TestModule = Framework.Modules.New( "TestModule", { "Base" } )

function TestModule:Think()	
	for i = 1, 1000 do
		K = math.sqrt( i )
	end
end

function TestModule:HUDPaint()
	surface.SetFont( "ChatFont" )
	surface.SetTextColor( 255, 255, 255, 255 )
	local i = 0
	for k, v in pairs( Framework.Modules.GetAll() ) do
		if( !v:IsRunning() ) then continue end
		i = i + 1
		Framework.Statistics.Plot( v:GetName() .. " Hook Data", STATISTIC_PLOT_PIE, 30, (160*i), 300, 150 )
	end
end

TestModule:Enable()
*/